package com.atguigu.tingshu.user.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.*;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.user.UserCollectVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private UserPaidTrackMapper userPaidTrackMapper;

    @Autowired
    private UserPaidAlbumMapper userPaidAlbumMapper;

    @Autowired
    private RsaSigner rsaSigner;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    /**
     * 普通的token和jwt生成的token的区别：
     * 1. 普通的token没有任何代表性 普通的token不安全
     * 2. jwt 有代表性（载荷中自定义信息）    更安全（可以防止被篡改）
     *
     * @param code
     * @return
     */
    @Override
    public Map<String, Object> wxLogin(String code) {

        String openid = "";
        HashMap<String, Object> map = new HashMap<>();

        // 1.判断code码是否存在
        if (StringUtils.isEmpty(code)) {
            throw new GuiguException(201, "code不存在");
        }

        // 2.调用微信服务端
        WxMaUserService userService = wxMaService.getUserService();
        WxMaJscode2SessionResult sessionInfo = null;
        try {
            sessionInfo = userService.getSessionInfo(code);
            openid = sessionInfo.getOpenid();
        } catch (WxErrorException e) {
            log.error("调用微信服务端失败:{}", e.getMessage());
            throw new GuiguException(201, "调用微信服务端失败");
        }


        // 先去查询缓存
        String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openid;
        String jsonWebTokenFromRedis = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(jsonWebTokenFromRedis)) {
            map.put("token", jsonWebTokenFromRedis);
            return map;
        }


        // 3.根据openid查询用户信息
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getWxOpenId, openid);
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        if (userInfo == null) {
            // 4.向user_info表中插入用户（注册用户信息）
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openid);
            userInfo.setNickname(System.currentTimeMillis() + "-【Recall】-" + UUID.randomUUID().toString().substring(0, 4).replace("-", ""));
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            int insert = userInfoMapper.insert(userInfo);
            log.info("注册用户：{}", insert > 0 ? "success" : "fail");

            // 5.向tingshu_account库中的user_account表中插入用户账户（初始化用户账户余额）
            rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());
            log.info("用户微服务发送初始化用户账户余额消息：{}成功", userInfo.getId());
        }


        // 6.得到一个jsonWebToken
        String token = getJsonWebToken(userInfo.getId(), openid);
        //存放在服务端的内存中 （Session） MySQL（一个微服务 各个微服务都能取到 比较的时候比较慢）   Redis（一个微服务 各个微服务都能取到 比较的时候比较快）
        map.put("token", token);

        // Redis常用的数据类型有五种（String List Set Zset Hash）
        // 7.将jsonWebToken存入redis中
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openid;


        // 给用户登录设置一个过期时间
        // 过期时间：长、短
        // 短：频繁登录       优点：安全一些（攻击的时间窗口较小）
        // 长：不用频繁登录    缺点：不太安全（攻击的时间窗口较大）
        // 再引入一个token （1.为了提高用户的体验 2. 安全）
        // 双token机制 accessToken（时间短，一般在分钟或者小时基本）和RefreshToken（时间长，一般在天或者月）

        redisTemplate.opsForValue().set(accessTokenKey, token, 20, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(refreshTokenKey, token, 180, TimeUnit.DAYS);

        // 8.返回jsonWebToken
        return map;
    }

    private String getJsonWebToken(Long userId, String openid) {
        // 定义一个载荷
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", userId);
        jsonObject.put("openId", openid);
        // jwt方式生成
        Jwt jwt = JwtHelper.encode(jsonObject.toString(), rsaSigner);
        String jsonWebToken = jwt.getEncoded();
        return jsonWebToken;
    }

    @Override
    public Map<String, Object> getNewAccessToken() {

        HashMap<String, Object> result = new HashMap<>();

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 1.2 获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();
        // 1.3 获取请求对象的请求头
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            throw new GuiguException(201, "之前没有登录过");
        }

        // 2.校验jsonWebToken是否被篡改了。
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(PublicConstant.PUBLIC_KEY));
        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        Object openId = map.get("openId");
        Object userId = map.get("id");


        String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId;
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;


        // 1.从Redis中获取RefreshToken
        String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(refreshToken)) {
            String jsonWebToken = getJsonWebToken(Long.parseLong(userId.toString()), String.valueOf(openId.toString()));
            redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 20, TimeUnit.DAYS);
            redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 180, TimeUnit.DAYS);
            result.put("token", jsonWebToken);
            return result;
        } else {
            // 去登录
            result.put("1", "v");
        }

        return result;
    }

    @Override
    public void updateUser(UserInfoVo userInfoVo) {
        // 查询用户信息
        Long userId = AuthContextHolder.getUserId();
        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (userInfo == null) {
            throw new GuiguException(201, "用户不存在");
        }
        userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        userInfo.setNickname(userInfoVo.getNickname());
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public UserInfoVo getUserInfo(Long userId) {
        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (userInfo == null) {
            throw new GuiguException(201, "该用户不存在");
        }
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo);
        return userInfoVo;
    }

    @Override
    public Map<Long, String> getUserPaidAlbumTrack(Long userId, Long albumId) {
        LambdaQueryWrapper<UserPaidTrack> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPaidTrack::getUserId, userId)
                .eq(UserPaidTrack::getAlbumId, albumId);
        List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(queryWrapper);
        return userPaidTrackList.stream().collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));
    }

    @Override
    public Boolean getUserPaidAlbum(Long userId, Long albumId) {
        LambdaQueryWrapper<UserPaidAlbum> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPaidAlbum::getUserId, userId)
                .eq(UserPaidAlbum::getAlbumId, albumId);
        return userPaidAlbumMapper.selectOne(queryWrapper) != null;
    }

    @Override
    public Boolean collect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);

        // 2.构建一个查询对象
        Query query = new Query(criteria);

        // 3.查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);

        // 4.判断
        if (count == 0) {
            // 收藏
            UserCollect userCollect = new UserCollect();
            userCollect.setId(ObjectId.get().toString());
            userCollect.setUserId(userId);
            userCollect.setTrackId(trackId);
            userCollect.setCreateTime(new Date());
            mongoTemplate.save(userCollect, collectionName);
            // 更新收藏量
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, 1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return true;
        } else {
            // 取消收藏
            mongoTemplate.remove(query, collectionName);
            // 更新收藏量
            // 更新收藏量
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, -1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return false;
        }
    }

    @Override
    public Boolean isCollect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);

        // 2.构建一个查询对象
        Query query = new Query(criteria);

        // 3.查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);
        return count > 0;
    }

    @Override
    public Boolean isSubscribe(Long albumId) {
        Long userId = AuthContextHolder.getUserId();
        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("albumId").is(albumId);

        // 2.构建一个查询对象
        Query query = new Query(criteria);

        // 3.查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId);
        long count = mongoTemplate.count(query, UserSubscribe.class, collectionName);
        return count > 0;

    }

    @Override
    public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> userCollectVoPage, Long userId) {

        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId);

        // 2.构建查询对象
        Query query = new Query(criteria);

        // 3.分页+排序
        // from:(pageNo - 1) * pageSize
        long size = userCollectVoPage.getSize();
        long from = (userCollectVoPage.getCurrent() - 1) * size;
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        PageRequest pageRequest = PageRequest.of((int) from, (int) size, sort);

        query.with(pageRequest);

        // 4.查询（查询满足条件的记录数）
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);

        long total = mongoTemplate.count(query.limit(-1), UserCollect.class, collectionName);

        // 5.查询数据
        List<UserCollect> userCollects = mongoTemplate.find(query, UserCollect.class, collectionName);

        // 6.获取收藏的所有声音id集合
        List<Long> trackIdList = userCollects.stream().map(UserCollect::getTrackId).toList();

        // 7.查询数据库获取到标题和封面
        if (!CollectionUtils.isEmpty(trackIdList)){
            Result<List<TrackListVo>> trackListVoResult=albumInfoFeignClient.getTrackList(trackIdList);
            List<TrackListVo> trackListVoResultData = trackListVoResult.getData();
            if (CollectionUtils.isEmpty(trackListVoResultData)){
                throw new GuiguException(201, "远程查询声音列表集合失败");
            }

            // 8.将声音集合转成Map
            Map<Long, TrackListVo> trackListVoMap = trackListVoResultData
                    .stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));

            List<UserCollectVo> userCollectVoList = userCollects.stream().map(userCollect -> {
                UserCollectVo userCollectVo = new UserCollectVo();
                TrackListVo trackListVo = trackListVoMap.get(userCollect.getTrackId());
                userCollectVo.setAlbumId(trackListVo.getAlbumId());
                userCollectVo.setTrackId(userCollect.getTrackId());
                userCollectVo.setCreateTime(userCollect.getCreateTime());
                userCollectVo.setTrackTitle(trackListVo.getTrackTitle());
                userCollectVo.setCoverUrl(trackListVo.getCoverUrl());
                return userCollectVo;
            }).toList();

            return userCollectVoPage.setRecords(userCollectVoList).setTotal(total);
        }
        return userCollectVoPage;
    }

    private TrackStatMqVo prepareCollectMsgDto(Long trackId, Integer count) {
        TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
        trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replace("-", ""));
        trackStatMqVo.setTrackId(trackId);
        trackStatMqVo.setStatType(SystemConstant.TRACK_STAT_COLLECT);
        trackStatMqVo.setCount(count);
        return trackStatMqVo;
    }
}
